package cn.iocoder.yudao.module.ai.service.busimodel;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.ai.controller.admin.busimodelparam.vo.BusiModelParamPageReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.busimodelparam.BusiModelParamDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.jupyter.JupyterDO;
import cn.iocoder.yudao.module.ai.dal.mysql.busimodelparam.BusiModelParamMapper;
import cn.iocoder.yudao.module.ai.service.jupyter.JupyterService;
import cn.iocoder.yudao.module.infra.util.DateUtils;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

import cn.iocoder.yudao.module.ai.controller.admin.busimodel.vo.*;
import cn.iocoder.yudao.module.ai.dal.dataobject.busimodel.BusiModelDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;

import cn.iocoder.yudao.module.ai.dal.mysql.busimodel.BusiModelMapper;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


/**
 * 业务模型 Service 实现类
 *
 * @author gt
 */
@Service
@Validated
@Slf4j
public class BusiModelServiceImpl implements BusiModelService {

    @Resource
    private BusiModelMapper busiModelMapper;
    @Resource
    private BusiModelParamMapper busiModelParamMapper;
    @Resource
    private JupyterService jupyterService;

    @Value("${yudao.file.upload-path}")
    private String fileUploadPath;

    @Value("${busiModle.build-path}")
    private String buildPath;

    @Value("${jupyter.env-file-name}")
    private String envFile;

    @Value("${jupyter.config-file-name}")
    private String sceneFile;


    @Override
    public Integer createBusiModel(BusiModelSaveReqVO createReqVO) {
        BusiModelDO busiModel = BeanUtils.toBean(createReqVO, BusiModelDO.class);
        busiModelMapper.insert(busiModel);
        List<BusiModelParamDO> busiModelParamDOList = createReqVO.getParams();
        if (ObjectUtil.isNotEmpty(busiModelParamDOList)) {
            busiModelParamDOList.forEach(modelParamDO -> modelParamDO.setBusiModelId(busiModel.getId()));
        }
        busiModelParamMapper.insertBatch(busiModelParamDOList);
        return busiModel.getId();
    }

    @Override
    public void updateBusiModel(BusiModelSaveReqVO updateReqVO) {
        BusiModelDO updateObj = BeanUtils.toBean(updateReqVO, BusiModelDO.class);
        List<BusiModelParamDO> params = updateReqVO.getParams();
        if (ObjectUtil.isNotEmpty(params)) {
            List<BusiModelParamDO> deleteParams = busiModelParamMapper.selectList(BusiModelParamDO::getBusiModelId, updateReqVO.getId());
            if (CollectionUtil.isNotEmpty(deleteParams))
                busiModelParamMapper.deleteBatchIds(deleteParams);
            params = params.stream().map(obj -> {
                obj.setId(null);
                obj.setBusiModelId(updateObj.getId());
                return obj;
            }).collect(Collectors.toList());
            busiModelParamMapper.insertBatch(params);
        }
        busiModelMapper.updateById(updateObj);
    }

    @Override
    public void deleteBusiModel(Integer id) {
        busiModelParamMapper.delete(BusiModelParamDO::getBusiModelId, id);
        busiModelMapper.deleteById(id);
    }


    @Override
    public BusiModelAndParamVO getBusiModel(Integer id) {
        BusiModelDO modelDO = busiModelMapper.selectById(id);
        List<BusiModelParamDO> params = busiModelParamMapper.selectParams(modelDO.getId(), null);
        BusiModelAndParamVO modelAndParamVO = BeanUtils.toBean(modelDO, BusiModelAndParamVO.class);
        modelAndParamVO.setParams(params);
        return modelAndParamVO;
    }

    @Override
    public PageResult<BusiModelDO> getBusiModelPage(BusiModelPageReqVO pageReqVO) {
        return busiModelMapper.selectPage(pageReqVO);
    }

    @Override
    public String buildBusinessModel(BusiModelDO busiModelDO, JupyterDO jupyterDO) throws IOException {

        Path workPath = Paths.get(jupyterDO.getWorkPath());
        Path dataPath = Paths.get(buildPath + "/" + DateUtils.datePath() + "/" + UUID.randomUUID());

        // 确保目标路径存在，如果不存在则创建
        try {
            if (!Files.exists(dataPath)) {
                Files.createDirectories(dataPath);
            }

            // 遍历源路径下的所有文件和目录
            Files.walkFileTree(workPath, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    // 创建目标目录（相对于源目录的路径）
                    Path targetDir = dataPath.resolve(workPath.relativize(dir));
                    if (!Files.exists(targetDir)) {
                        Files.createDirectories(targetDir);
                    }
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    // 复制文件到目标路径
                    Path targetFile = dataPath.resolve(workPath.relativize(file));
                    Files.copy(file, targetFile, StandardCopyOption.REPLACE_EXISTING);
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                    // 处理文件访问失败的情况
                    log.info("Failed to visit file: " + file + " (" + exc.getMessage() + ")");
                    return FileVisitResult.CONTINUE;
                }
            });

            log.info("Files copied successfully.");
        } catch (IOException e) {
            e.printStackTrace();
        }

        //重新填充env.sh
        Path envPath = jupyterService.findConfigFileWithWalk(dataPath, envFile);
        if (ObjectUtil.isNotEmpty(envPath) && ObjectUtil.isNotNull(busiModelDO)) {
            List<BusiModelParamDO> envParams = busiModelParamMapper.selectParams(busiModelDO.getId(), "evnParam");
            buildEnvFile(envParams, envPath);
            log.info(" env.sh 文件已填充：{}", JSONObject.toJSONString(envParams));
        }

        //重新填充scene.conf
        Path scenePath = jupyterService.findConfigFileWithWalk(dataPath, sceneFile);
        if (ObjectUtil.isNotEmpty(scenePath) && ObjectUtil.isNotNull(busiModelDO)) {
            List<BusiModelParamDO> sceneParams = busiModelParamMapper.selectParams(busiModelDO.getId(), "scenePath");
            buildSceneFile(sceneParams, scenePath);
            log.info(" scene.conf 文件已填充：{}", JSONObject.toJSONString(sceneParams));
        }
        String zipFile = "";
        if (ObjectUtil.isNotNull(busiModelDO)) {
            zipFile = zipDirectory(dataPath, Paths.get(buildPath + "/" + DateUtils.datePath() + "/" + busiModelDO.getModelName() + ".zip"));
        } else {
            zipFile = zipDirectory(dataPath, Paths.get(buildPath + "/" + DateUtils.datePath() + "/" + new Date().getTime()+".zip"));
        }

        log.info("构建后的业务模型路径：" + zipFile);
        return zipFile;

    }

    private void buildEnvFile(List<BusiModelParamDO> envParams, Path envPath) {
        Map<String, String> paramMap = envParams.stream()
                .collect(Collectors.toMap(
                        BusiModelParamDO::getParamName,
                        BusiModelParamDO::getDefaultValue
                ));
        try {
            Files.write(envPath, paramMap.entrySet().stream()
                            .map(entry -> entry.getKey() + "=" + entry.getValue())
                            .collect(Collectors.joining("\n"))
                            .getBytes(),
                    StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

            log.info("Map 被写入到文件: " + envPath);
        } catch (IOException e) {
            System.err.println("写入文件失败: " + e.getMessage());
            e.printStackTrace();
        }

    }

    private void buildSceneFile(List<BusiModelParamDO> envParams, Path scenePath) {
        Map<String, String> paramMap = envParams.stream()
                .collect(Collectors.toMap(
                        BusiModelParamDO::getParamName,
                        BusiModelParamDO::getDefaultValue
                ));
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            String jsonString = objectMapper.writeValueAsString(paramMap);
            Files.write(scenePath, jsonString.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

            log.info("JSON 被写入到文件: " + scenePath);
        } catch (IOException e) {
            log.info("写入文件失败: " + e.getMessage());
            e.printStackTrace();
        }
    }

    private String zipDirectory(Path sourceDirPath, Path zipFilePath) throws IOException {
        try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFilePath))) {
            Files.walkFileTree(sourceDirPath, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    Path targetFile = sourceDirPath.relativize(file);
                    zipOut.putNextEntry(new ZipEntry(targetFile.toString()));
                    Files.copy(file, zipOut);
                    zipOut.closeEntry();
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    Path targetDir = sourceDirPath.relativize(dir);
                    zipOut.putNextEntry(new ZipEntry(targetDir.toString() + "/"));
                    zipOut.closeEntry();
                    return FileVisitResult.CONTINUE;
                }
            });
        }
        return zipFilePath.toString();
    }

}